package com.bullardev.xldatacluster.providers;

import java.io.StringWriter;
import java.net.Authenticator;
import java.net.PasswordAuthentication;
import java.net.URL;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;

import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.ws.BindingProvider;

import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.NodeList;

import com.bullardev.xldatacluster.dataset.DataSet;
import com.bullardev.xldatacluster.dataset.DataSet.Row;
import com.bullardev.xldatacluster.dataset.DataSetProvider;
import com.bullardev.xldatacluster.dataset.DataSetProvider.DataSetProviderConfig;
import com.bullardev.xldatacluster.dataset.DataSetProviderFactory.DataSetProviderFactoryConfig;
import com.bullardev.xldatacluster.providers.webservice.lists.GetListItems;
import com.bullardev.xldatacluster.providers.webservice.lists.GetListItemsResponse;
import com.bullardev.xldatacluster.providers.webservice.lists.Lists;
import com.bullardev.xldatacluster.providers.webservice.lists.ListsSoap;
import com.sun.org.apache.xerces.internal.dom.ElementNSImpl;

/**
 *
 * @author Mubin
 */

public class SharePointUtility {

    /**
     * Instance of this class.
     */
    private static final SharePointUtility INSTANCE =
                                new SharePointUtility();

    /**
     *
     * @return SharePointUtility
     */
    public static SharePointUtility getInstance() {
        return INSTANCE;
    }
    /**
     *
     * @return SharePointInterface
     */
    public final SharePointInterface getSPInstance() {
        return new MyClient();
    }


    /**
     * @param args Command line arguments.
     * @throws Exception Exception
     *
     */
    public static void main(final String[] args) throws Exception {
        SharePointDataSetProvider spdsp = SharePointDataSetProvider.
                                                        getInstance();
        Properties props = new Properties();
        String wsdlURL = "http://sp.demo.bullardev.com/_vti_bin/"
                                                + "Lists.asmx?WSDL";
        String mywsdlURL = "http://localhost/_vti_bin/Lists.asmx?WSDL";
        String username = "Yousuf";
        String password = "letmein";
        String myusername = "ibm";
        String mypassword = "welcome2ibm";
        //String domain = "IBM-pc";
        //props.put("WSDL_URL", "");
        props.put("WSDL_URL", wsdlURL);
        //props.put("DOMAIN", "IBM-pc");
        props.put("USER_NAME", username);
        props.put("PASSWORD", password);

        DataSetProviderFactoryConfig factConfig = spdsp.createConfig(props);
        DataSetProvider provider = spdsp.createInstance(factConfig);
        props.clear();
        props.put("LIST_NAME", "All Feilds test List");
        //props.put("HIDDEN_FIELDS", "true");
        DataSetProviderConfig dataConfig = provider.createConfig(props);
        DataSet ds = provider.createDataSet(dataConfig, null);
        //System.out.println(ds.getColumnType(2));
        System.out.println("Row count: " + ds.getRowCount());

        List<String> allCols = new ArrayList<String>(ds.getColumnCount());
        for (int index = 0; index < ds.getColumnCount(); index++) {
            allCols.add(ds.getColumnName(index));
        }

        List<DataSet.eDataType> allColsDatatype = new
                    ArrayList<DataSet.eDataType>(ds.getColumnCount());
        for (int index = 0; index < ds.getColumnCount(); index++) {
            allColsDatatype.add(ds.getColumnType(index));
        }
        for (Row row : ds) {
            String thisRow[] = new String[ds.getColumnCount()];
            int ij = 0;
            for (Iterator iterator = allCols.iterator(), itr2 = allColsDatatype.iterator();
                    iterator.hasNext() && itr2.hasNext();) {
                String colName = (String)iterator.next();
                    DataSet.eDataType dataType = (DataSet.eDataType) itr2.next();
                    Object colValue = row.getObject(colName);
                    System.out.print("\t"+colValue);
                    /*thisRow[ij++] = colValue;
                    System.out.print("DataType: " + dataType
                            + "=== " + row.getString(colName)
                            + " -- ");*/
            }
            System.out.println();
        }
    }

    /**
     *
     * @author Mubin
     *
     */
    private final class MyClient implements SharePointInterface {


        /**
         *
         */
        private List<String> columnNames = new ArrayList<String>();

        /**
         *
         */
        private Properties connectionProps = new Properties();

        /**
         *
         */
        private List<String> dataTypes = new ArrayList<String>();
        /**
         * Making the default construction unavailable.
         */
        private MyClient() {
        }


        /**
         *
         * @return List of column names
         */
        @Override
        public List<String> getColumnNames() {
            return columnNames;
        }

        /**
         *
         * @return List of datatypes
         */
        @Override
        public List<String> getDataTypes() {
            return dataTypes;
        }


        /**
         * Creates a port connected to the SharePoint Web Service given.
         * Authentication is done here.
         *
         * @param wsdlURL URL of the Sharepoint WSDL
         * @param username
         *            SharePoint username
         * @param password
         *            SharePoint password
         * @return port ListsSoap port, connected with SharePoint
         * @throws Exception
         *             in case of invalid parameters or connection error.
         */
        @Override
        public ListsSoap sharePointListsAuth(final String wsdlURL,
                final String username, final String password) throws Exception {

            connectionProps.put("WSDL_URL", wsdlURL);
            connectionProps.put("USER_NAME", username);
            connectionProps.put("PASSWORD", password);

            ListsSoap port = null;

            Authenticator.setDefault(new Authenticator() {
                @Override
                protected PasswordAuthentication getPasswordAuthentication() {
                    return new PasswordAuthentication(username, password
                            .toCharArray());
                }
            });
            if (username != null && password != null) {
                try {
                    Lists service = new Lists(new URL(wsdlURL));
                    port = service.getListsSoap();
                    ((BindingProvider) port).getRequestContext().put(
                            BindingProvider.USERNAME_PROPERTY, username);
                    ((BindingProvider) port).getRequestContext().put(
                            BindingProvider.PASSWORD_PROPERTY, password);
                } catch (Exception e) {
                    throw new Exception("Error: " + e.toString());
                }
            } else {
                throw new Exception(
                        "Couldn't authenticate: Invalid"
                                + " connection details given.");
            }
            return port;
        }


        /**
         * Connects to a SharePoint Lists Web Service,
         * and reads all the elements of the given list.
         *
         * @param port
         *            an already authenticated SharePoint Online SOAP port
         * @param listName
         *            original name of the Sharepoint list
         *            that is going to be read
         * @param viewName
         *            view name
         * @param rowLimit
         *            limits the number of rows (list items)
         *            that are going to be returned
         * @param includeHiddenFields Include Hidden fields?
         * @throws Exception if something goes wrong
         * @return List List of the data retrieved from SharePoint
         */
        @Override
        public List<Object[]> retrieveDataFromList(final ListsSoap port,
                final String listName, final String viewName,
                            final String rowLimit,
                           final boolean includeHiddenFields) throws Exception {

                String tempViewName = viewName;
                if (viewName == null) {
                    tempViewName = "";
                }

                List<Object[]> data = null;
                columnNames.clear();
                //noOfColumns = 0;
            if (port != null && listName != null && rowLimit != null) {
                try {

                    // Here are additional parameters that may be set
                    GetListItems.ViewFields viewFields = null;
                    GetListItems.Query query = null;
                    GetListItems.QueryOptions queryOptions = null;
                    String webID = "";

                    System.out.println(listName + "" + tempViewName);
                    connectionProps.put("LIST_NAME", listName);
                    connectionProps.put("VIEW_NAME", tempViewName);
                    SPMetaData metaData = new SPMetaData(connectionProps);
                    /*System.out.println(metaData.getAllListNames());
                    System.out.println(metaData.getAllListNames().size());*/
                    columnNames.addAll(metaData.getColumnLabels());
                    dataTypes.addAll(metaData.getDataTypes());

                    if (!includeHiddenFields) {
                        int index = columnNames.indexOf("ID");
                        if (index >= 0) {
                            columnNames.remove(index);
                            dataTypes.remove(index);
                        }
                        index = columnNames.indexOf("Attachments");
                        if (index >= 0) {
                            columnNames.remove(index);
                            dataTypes.remove(index);
                        }
                    }

                    GetListItemsResponse.GetListItemsResult result = port
                            .getListItems(listName, "", query, viewFields,
                                    rowLimit, queryOptions, webID);

                    Object listResult = result.getContent().get(0);

                    if ((listResult != null)
                            && (listResult instanceof ElementNSImpl)) {
                        ElementNSImpl node = (ElementNSImpl) listResult;
                        //System.out.println("3");
                        Document doc = node.getOwnerDocument();
                        //System.out.println(xmlToString(doc));
                        // selects a list of nodes which have z:row elements
                        NodeList list = node.getElementsByTagName("z:row");

                        data = new ArrayList<Object[]>();
                        for (int i = 0; i < list.getLength(); i++) {

                            // Gets the attributes of the current row/element
                            NamedNodeMap attributes = list.item(i).
                                                       getAttributes();

                            Object[] thisRow = new Object[columnNames.size()];
                            //System.out.println("columnNames: "+columnNames);
                            List<String> tempList = metaData.getColumnNames();
                            //System.out.println("TempList: "+tempList);
                            if (!includeHiddenFields) {
                                int index = tempList.indexOf("ID");
                                if (index != -1) {
                                    tempList.remove(index);
                                }
                                index = tempList.indexOf("Attachments");
                                if (index != -1) {
                                    tempList.remove(index);
                                }
                            }
                            /*System.out.println("TempList: " + tempList);
                            System.out.println("Data types: " + dataTypes);
                            System.out.println("Column Names: "
                             + columnNames);*/
                            for (int k = 0; k < tempList.size(); k++) {
                                String internalColumnName = "ows_"
                                                    + tempList.get(k);
                                if (attributes.getNamedItem(internalColumnName)
                                        != null) {
                                    String val = attributes.getNamedItem(
                                            internalColumnName)
                                            .getNodeValue();
                                    if ("Calculated".
                                          equalsIgnoreCase(dataTypes.get(k))) {
                                        if (metaData.isCalculated(tempList
                                                .get(k))
                                                && "Number".
                                                equalsIgnoreCase(metaData
                                                        .getResultType(tempList
                                                                .get(k)))) {
                                            val = val.substring(val
                                                    .lastIndexOf("#") + 1);
                                            Double d = Double.parseDouble(val);
                                            if (metaData.
                                                isPercentage(tempList.get(k))) {
                                                d = d * 100;
                                            }
                                            thisRow[k] = d;
                                            //dataTypes.add(k, "Number");
                                        }
                                    } else if ("Number".
                                           equalsIgnoreCase(dataTypes.get(k))
                                           || "Currency".
                                           equalsIgnoreCase(dataTypes.get(k))) {
                                        double d = Double.parseDouble(val);
                                        thisRow[k] = d;
                                    } else if ("DateTime".
                                          equalsIgnoreCase(dataTypes.get(k))) {
                                        DateFormat df = new SimpleDateFormat(
                                                        "yyyy-MM-dd hh:mm:ss");
                                        java.util.Date d = df.parse(val);
                                        java.sql.Timestamp t =
                                            new java.sql.Timestamp(d.getTime());
                                        thisRow[k] = t;
                                     } else if ("Boolean".
                                           equalsIgnoreCase(dataTypes.get(k))) {
                                         if ("true".equalsIgnoreCase(val)
                                               || "yes".equalsIgnoreCase(val)) {
                                             thisRow[k] = true;
                                         } else {
                                             thisRow[k] = false;
                                         }
                                     } else {
                                        thisRow[k] = val;
                                    }
                                   /*System.out.println(attributes.item(k)
                                            + ": "
                                            + attributes.getNamedItem(
                                                    internalColumnName)
                                                    .getNodeValue());*/
                                } else {
                                    /*throw new Exception("Couldn't find the '"
                                      + attributes.item(k) + "' column in the '"
                                      + listName + "' list in SharePoint.\n");*/
                                  //Lets do nothing if we don't find one column
                                    thisRow[k] = "";
                                }
                            }
                            data.add(thisRow);
                        }
                    } else {
                        throw new Exception(
                                listName
                                        + " list response from SharePoint "
                                        + "is either null or corrupt\n");
                    }
                } catch (Exception ex) {
                    throw new Exception("Exception. See stacktrace."
                            + ex.toString() + "\n");
                }
            }

            for (int i = 0; i < columnNames.size(); i++) {
                //String colName = (String) columnNames.get(i);
                if ("Calculated".
                        equalsIgnoreCase(dataTypes.get(i))) {
                          dataTypes.add(i, "Number");
                  }
            }
            return data;
        }

        /**
         * @throws Exception some error
         *
         */



    }

    /**
     * Creates a string from an XML file with start and end indicators.
     * @param docToString document to convert
     * @return string of the xml document
     */
    public static String xmlToString(final Document docToString) {
        String returnString = "\n-------------- XML START --------------\n";
        try {
            //create string from xml tree
            //Output the XML
            //set up a transformer
            TransformerFactory transfac = TransformerFactory.newInstance();
            Transformer trans;
            trans = transfac.newTransformer();
            trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
            trans.setOutputProperty(OutputKeys.INDENT, "yes");
            StringWriter sw = new StringWriter();
            StreamResult streamResult = new StreamResult(sw);
            DOMSource source = new DOMSource(docToString);
            trans.transform(source, streamResult);
            String xmlString = sw.toString();
            //print the XML
            returnString = returnString + xmlString;
        } catch (TransformerException ex) {
            ex.printStackTrace();
        }
        returnString = returnString + "-------------- XML END --------------";
        return returnString;
    }

}
